package ;
import Files;
import HLex;
import HLlr;
import Main;
import Scope;
import Subs;
import Writer;





class Macro {
    static public inline var MIXIN = 0;
    static public inline var DEFINE = 1;
    static public inline var EXPAND = 2;
    
    static var nextId = 0;
    public var id:Int;
    
    public var type:Int;
    public var newfile:Bool; 
    public var preamble:Array<State>; 

    public var post:Array<State>;
    
    public var name:String;
    public var argc:Int;
	
	public var instances:Hash<Array<State>>;
	
	public var owner:Scope;
    
    public function new() {
		instances = new Hash<Array<State>>();
    }
    public function init(x:State) {
        switch(x) {
            case sMixin(_,name,args,post,_):
                type = MIXIN;
                this.name = name;
                argc = args.length;
                this.post = post;
            case sDefine(_,nf,pre,name,args,post,_):
                type = DEFINE;
                newfile = nf;
                preamble = pre;
                this.name = name;
                argc = args.length;
                this.post = post;
            case sExpand(_,name,args,post,_):
                type = EXPAND;
                this.name = name;
                argc = args.length;
                this.post = post;
            default:
        };
        id = nextId++;
    }
       
    public function instantiate(args:Array<Array<State>>, cscope:Scope) {
		switch(type) {
			case MIXIN:
				return inst(post,args,cscope,false,true);
			case DEFINE:
				var key = name+"_";
				for(i in args) key += Writer.printstates(i);
				key = (~/[^a-zA-Z0-9_]/g).replace(key,'_');
				if(!instances.exists(key)) {
					instances.set(key,null);
					var xs = inst(preamble,args,owner,false,true);
					xs.push(sIdent(key));
					xs = xs.concat(inst(post,args,owner,false,true));
					instances.set(key,Subs.subs(xs,owner.macros_in_scope(),owner));
				}
				return [sIdent(key)];
			case EXPAND:
				var key = name+"_";
				for(i in args) key += Writer.printstates(i);
				if(!instances.exists(key)) {
					instances.set(key,null);
					var xs = inst(post,args,cscope,false,true);
					instances.set(key,Subs.subs(xs,owner.macros_in_scope(),owner));
				}
				return [];
			default:
				return [];
		}
    }
    
    public function inst(post:Array<State>,args:Array<Array<State>>, cscope:Scope, ?arg:Bool=false, ?fst:Bool=false) {
        var ret = [];
        
        var preident:String = null;
        var precons:Bool = false;
        
        
        for(x in post) {
            switch(x) {
            case sArgument(mac,n):
                if(mac==id) {
                    for(i in args[n]) {
						
						switch(i) {
							case sRealScope(s):
								var s2 = Reflect.copy(s);
								s2.parent = cscope;
								 {
            var x = (sRealScope(s2));
            ret.push(x);
        };
							case sMixin(acc,n,A,B,mac):
								var mac2 = Reflect.copy(mac);
								 {
							if(mac2.owner == Main.mIndet) {
								var sacc = if(acc==null) fst ? null : cscope
								else {
									switch(acc) {
										case sLocal:  fst ? null : cscope;
										case sGlobal: null;
										case sFile:   null;
										case sObject: fst ? null : cscope.object;
										case sMethod: fst ? null : cscope.method;
									}
								};
								if(sacc==null || sacc==Main.mIndet) {
									trace(n+" IMPOSSIBLE SCOPE IN INSTANTIATION");
								}else {
									trace(n+" "+sacc);
									mac2.owner = sacc;
									sacc.macros.push(mac2);
								}
							}
						};
								 {
            var x = (sMixin(acc,n,A,B,mac2));
            ret.push(x);
        };
							case sExpand(acc,n,A,B,mac):
								var mac2 = Reflect.copy(mac);
								 {
							if(mac2.owner == Main.mIndet) {
								var sacc = if(acc==null) fst ? null : cscope
								else {
									switch(acc) {
										case sLocal:  fst ? null : cscope;
										case sGlobal: null;
										case sFile:   null;
										case sObject: fst ? null : cscope.object;
										case sMethod: fst ? null : cscope.method;
									}
								};
								if(sacc==null || sacc==Main.mIndet) {
									trace(n+" IMPOSSIBLE SCOPE IN INSTANTIATION");
								}else {
									trace(n+" "+sacc);
									mac2.owner = sacc;
									sacc.macros.push(mac2);
								}
							}
						};
								 {
            var x = (sExpand(acc,n,A,B,mac2));
            ret.push(x);
        };
							case sDefine(acc,C,D,n,A,B,mac):
								var mac2 = Reflect.copy(mac);
								 {
							if(mac2.owner == Main.mIndet) {
								var sacc = if(acc==null) fst ? null : cscope
								else {
									switch(acc) {
										case sLocal:  fst ? null : cscope;
										case sGlobal: null;
										case sFile:   null;
										case sObject: fst ? null : cscope.object;
										case sMethod: fst ? null : cscope.method;
									}
								};
								if(sacc==null || sacc==Main.mIndet) {
									trace(n+" IMPOSSIBLE SCOPE IN INSTANTIATION");
								}else {
									trace(n+" "+sacc);
									mac2.owner = sacc;
									sacc.macros.push(mac2);
								}
							}
						};
								 {
            var x = (sDefine(acc,C,D,n,A,B,mac2));
            ret.push(x);
        };
							default:
								 {
            var x = (Reflect.copy(i));
            ret.push(x);
        };
						}
					}
                }else  {
            var x = (sArgument(mac,n));
            ret.push(x);
        };
            case sRealScope(s):
                var s2 = Reflect.copy(s);
				s2.parent = cscope;
                s2.data = inst(s2.data,args,s2);
                for(i in s2.macros)
                    i.post = inst(i.post,args,s2);
                 {
            var x = (sRealScope(s2));
            ret.push(x);
        };
            case sCall(iargs):
                var sc = [];
                for(i in iargs) sc.push(inst(i,args,cscope,true));
                 {
            var x = (sCall(sc));
            ret.push(x);
        };
            case sMixin(acc,n,iargs,post,mac):
				 {
			if(mac.owner == Main.mIndet && !arg) {
				var sacc = if(acc==null) fst ? null : cscope
				else {
					switch(acc) {
						case sLocal:  fst ? null : cscope;
						case sGlobal: null;
						case sFile:   null;
						case sObject: fst ? null : cscope.object;
						case sMethod: fst ? null : cscope.method;
					}
				};
				if(sacc==null || sacc==Main.mIndet) {
					trace(n+" IMPOSSIBLE SCOPE IN INSTANTIATION");
				}else {
					trace(n+" "+sacc);
					mac.owner = sacc;
					sacc.macros.push(mac);
				}
			}else {}
		};
                 {
            var x = (sMixin(acc,n,iargs,inst(post,args,cscope),mac));
            ret.push(x);
        };
			case sExpand(acc,n,iargs,post,mac):
				 {
			if(mac.owner == Main.mIndet && !arg) {
				var sacc = if(acc==null) fst ? null : cscope
				else {
					switch(acc) {
						case sLocal:  fst ? null : cscope;
						case sGlobal: null;
						case sFile:   null;
						case sObject: fst ? null : cscope.object;
						case sMethod: fst ? null : cscope.method;
					}
				};
				if(sacc==null || sacc==Main.mIndet) {
					trace(n+" IMPOSSIBLE SCOPE IN INSTANTIATION");
				}else {
					trace(n+" "+sacc);
					mac.owner = sacc;
					sacc.macros.push(mac);
				}
			}else {}
		};
                 {
            var x = (sExpand(acc,n,iargs,inst(post,args,cscope),mac));
            ret.push(x);
        };
			case sDefine(acc,C,D,n,iargs,post,mac):
				var mac2 = Reflect.copy(mac);
				mac2.owner = Main.mIndet;
				 {
			if(mac2.owner == Main.mIndet && !arg) {
				var sacc = if(acc==null) fst ? null : cscope
				else {
					switch(acc) {
						case sLocal:  fst ? null : cscope;
						case sGlobal: null;
						case sFile:   null;
						case sObject: fst ? null : cscope.object;
						case sMethod: fst ? null : cscope.method;
					}
				};
				if(sacc==null || sacc==Main.mIndet) {
					trace(n+" IMPOSSIBLE SCOPE IN INSTANTIATION");
				}else {
					trace(n+" "+sacc);
					mac2.owner = sacc;
					sacc.macros.push(mac2);
				}
			}else {}
		};
                 {
            var x = (sDefine(acc,C,D,n,iargs,inst(post,args,cscope),mac2));
            ret.push(x);
        };
            default:
                 {
            var x = (Reflect.copy(x));
            ret.push(x);
        };
            }
        }
		

        return ret;
    }
}
